#include "imageinstancedata.h"
#include "../share/global.h"
#include "../MainStation/mainwindow.h"

#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmimgle/dcmimage.h"
#include "dcmtk/dcmdata/dcfilefo.h"
#include "dcmtk/dcmdata/dcdeftag.h"
#include "dcmtk/dcmdata/dcuid.h"

#include <QFile>

ImageInstanceData::ImageInstanceData(const QString &file):
    rawType(false),
    rawDcmff(0),
    rawImage(0),
    cachedRawCenter(0),
    cachedRawWidth(0),
    dcmff(0),
    dcmImage(0),
    cachedCenter(0),
    cachedWidth(0),
    filePath(file)
{
    dcmff = new DcmFileFormat;
    dcmff->loadFile(QString("%1/%2").arg(mainWindow->getDbConfig().imagePath, file).toLocal8Bit());
    initImage();
}

ImageInstanceData::ImageInstanceData(DcmFileFormat *dff):
    rawType(false),
    rawDcmff(0),
    rawImage(0),
    cachedRawCenter(0),
    cachedRawWidth(0),
    dcmff(dff),
    dcmImage(0),
    cachedCenter(0),
    cachedWidth(0)
{
    initImage();
}

ImageInstanceData::~ImageInstanceData()
{
    delete rawImage;
    delete dcmImage;
    delete rawDcmff;
    delete dcmff;
}

bool ImageInstanceData::isNormal() const
{
    if (rawType) {
        return rawImage && (rawImage->getStatus()==EIS_Normal);
    } else {
        return dcmImage && (dcmImage->getStatus() == EIS_Normal);
    }
}

bool ImageInstanceData::hasRawImage() const
{
    return QFile(rawFile).exists();
    /*
    if (!rawFile.isEmpty()) {
        if (rawImage) return true;

        rawImage = new DicomImage(rawFile.toLocal8Bit().data());
        if (EIS_Normal == rawImage->getStatus()) {
            return true;
        } else {
            delete rawImage;
            rawImage = 0;
        }
    }

    return false;
    */
}

void ImageInstanceData::initImage()
{
    DcmDataset *dset;
    OFCondition result;
    if (dcmff && (dset = dcmff->getDataset())) {
        //patient information
        const char *value = NULL;
        result = dset->findAndGetString(DCM_PatientID, value);
        patientID = QString::fromLocal8Bit(value);

        result = dset->findAndGetString(DCM_PatientName, value);
        patientName = QString::fromLocal8Bit(value);

        result = dset->findAndGetString(DCM_PatientAge, value);
        patientAge = QString::fromLocal8Bit(value);

        result = dset->findAndGetString(DCM_PatientBirthDate, value);
        patientBirth = QDate::fromString(QString::fromLocal8Bit(value),
                                         DATE_DICOM_FORMAT);

        result = dset->findAndGetString(DCM_PatientSex, value);
        patientSex = QString::fromLocal8Bit(value);

        //study information
        result = dset->findAndGetString(DCM_StudyInstanceUID, value);
        studyUid = QString::fromLatin1(value);

        result = dset->findAndGetString(DCM_StudyDate, value);
        studyTime.setDate(QDate::fromString(QString::fromLocal8Bit(value),
                                            DATE_DICOM_FORMAT));

        result = dset->findAndGetString(DCM_StudyTime, value);
        studyTime.setTime(formatDicomTime(QString::fromLatin1(value)));

        result = dset->findAndGetString(DCM_StudyDescription, value);
        studyDes = QString::fromLocal8Bit(value);

        result = dset->findAndGetString(DCM_ProtocolName, value);
        procId = QString::fromLocal8Bit(value);

        result = dset->findAndGetString(DCM_BodyPartExamined, value);
        bodyPart = QString::fromLocal8Bit(value);

        result = dset->findAndGetString(DCM_PatientPosition, value);
        bodyPos = QString::fromLocal8Bit(value);

        result = dset->findAndGetString(DCM_Manufacturer, value);
        manufacturer = QString::fromLocal8Bit(value);

        result = dset->findAndGetString(DCM_Modality, value);
        modality = QString::fromLocal8Bit(value);

        result = dset->findAndGetString(DCM_ManufacturerModelName, value);
        modelName = QString::fromLocal8Bit(value);

        //series information
        result = dset->findAndGetString(DCM_SeriesInstanceUID, value);
        seriesUid = QString::fromLatin1(value);

        result = dset->findAndGetString(DCM_SeriesNumber, value);
        seriesNumber = QString::fromLatin1(value);

        result = dset->findAndGetString(DCM_SeriesDescription, value);
        seriesDes = QString::fromLocal8Bit(value);

        //instance information
        result = dset->findAndGetString(DCM_SOPClassUID, value);
        sopClassUid = QString::fromLatin1(value);

        result = dset->findAndGetString(DCM_SOPInstanceUID, value);
        instanceUid = QString::fromLatin1(value);

        result = dset->findAndGetString(DCM_InstanceNumber, value);
        instanceNumber = QString::fromLatin1(value);

        result = dset->findAndGetString(DCM_InstitutionName, value);
        institution = QString::fromLocal8Bit(value);

        result = dset->findAndGetString(DCM_AcquisitionDate, value);
        if (value==NULL) result = dset->findAndGetString(DCM_ContentDate, value);
        acquisitionTime.setDate(QDate::fromString(QString::fromLatin1(value),
                                                  DATE_DICOM_FORMAT));

        result = dset->findAndGetString(DCM_AcquisitionTime, value);
        if (value==NULL) result = dset->findAndGetString(DCM_ContentTime, value);
        acquisitionTime.setTime(formatDicomTime(QString::fromLatin1(value)));

        result = dset->findAndGetString(DCM_PatientPosition, value);
        patientPostion = QString::fromLatin1(value);

        result = dset->findAndGetString(DCM_RequestingPhysician, value);
        reqPhysician = QString::fromLocal8Bit(value);

        result = dset->findAndGetString(DCM_PerformingPhysicianName, value);
        perPhysician = QString::fromLocal8Bit(value);

        result = dset->findAndGetString(DCM_AccessionNumber, value);
        accessionNumber = QString::fromLatin1(value);

        result = dset->findAndGetFloat64(DCM_PixelSpacing, pixelSpacingY, 0);
        result = dset->findAndGetFloat64(DCM_PixelSpacing, pixelSpacingX, 1);

        result = dset->findAndGetString(DCM_KVP, value);
        kvp = QString::fromLatin1(value).toDouble();
        result = dset->findAndGetString(DCM_Exposure, value);
        mAs = QString::fromLatin1(value).toDouble();
        result = dset->findAndGetString(DCM_ExposureTime, value);
        mS = QString::fromLatin1(value).toDouble();
        result = dset->findAndGetString(DCM_XRayTubeCurrent, value);
        mA = QString::fromLatin1(value).toDouble();

        result = dset->findAndGetFloat64(DCM_WindowWidth, winWidth);
        result = dset->findAndGetFloat64(DCM_WindowCenter, winCenter);
        defCenter = winCenter;
        defWidth = winWidth;

        if ((!filePath.isEmpty()) && (UID_DigitalXRayImageStorageForPresentation == sopClassUid)) {
            DcmItem *refItem;
            const char *refImageUid;
            dset->findAndGetSequenceItem(DCM_ReferencedImageSequence, refItem);
            if (refItem && refItem->findAndGetString(DCM_ReferencedSOPInstanceUID, refImageUid).good()) {
                QString dirName = filePath.left(filePath.lastIndexOf('/'));
                rawFile = QString("%1/%2_%3.dcm").arg(dirName, RAW_IMAGE_PREFIX, refImageUid);
                rawCenter = winCenter;
                rawWidth = winWidth;
            }
        }

        dcmImage = new DicomImage(dcmff, dset->getOriginalXfer());
        if (dcmImage->getStatus() == EIS_Normal) {
            if (winWidth < 1) {
                dcmImage->setRoiWindow(0, 0, dcmImage->getWidth(), dcmImage->getHeight());
                dcmImage->getWindow(winCenter, winWidth);
                defCenter = winCenter;
                defWidth = winWidth;
            }
            getPixmap(cachedPixmap);
        }
    }
}

void ImageInstanceData::setRoiWindow(const QRectF &rect)
{
    if (rawType && rawImage) {
        rawImage->setRoiWindow(rect.left(), rect.top(), rect.width(), rect.height());
        rawImage->getWindow(rawCenter, rawWidth);
    } else if (dcmImage) {
        dcmImage->setRoiWindow(rect.left(), rect.top(), rect.width(), rect.height());
        dcmImage->getWindow(winCenter, winWidth);
    }
}

void ImageInstanceData::setPolarity(EP_Polarity polarity)
{
    if (rawType && rawImage) {
        rawImage->setPolarity(polarity);
        dcm2bmpHelper(*rawImage, cachedRawPixmap);
    } else if (dcmImage) {
        dcmImage->setPolarity(polarity);
        dcm2bmpHelper(*dcmImage, cachedPixmap);
    }
}

EP_Polarity ImageInstanceData::getPolarity() const
{
    if (rawType && rawImage) return rawImage->getPolarity();
    else if (dcmImage) return dcmImage->getPolarity();
    return EPP_Normal;
}

const DicomImage *ImageInstanceData::getDcmImage() const
{
    if (rawType) return rawImage;
    else return dcmImage;
}

DcmFileFormat *ImageInstanceData::getFileFormat() const
{
    if (rawType) return rawDcmff;
    else return dcmff;
}

bool ImageInstanceData::saveFileFormat()
{
    DcmDataset *dset;
    DicomImage *image, *tmp;
    if (rawType && rawDcmff) {
        dset = rawDcmff->getDataset();
        image = new DicomImage(rawDcmff, dset->getOriginalXfer());
        tmp = rawImage;
        rawImage = image;
        delete tmp;
        dset->findAndGetFloat64(DCM_WindowWidth, rawWidth);
        dset->findAndGetFloat64(DCM_WindowCenter, rawCenter);
        rawImage->setWindow(rawCenter, rawWidth);
        if (cachedRawCenter == rawCenter && cachedRawWidth == rawWidth)
            dcm2bmpHelper(*rawImage, cachedRawPixmap);
        if (!rawFile.isEmpty())
            return EC_Normal == rawDcmff->saveFile(QString("%1/%2").arg(mainWindow->getDbConfig().imagePath, rawFile).toLocal8Bit().data());
    } else if (dcmff) {
        dset = dcmff->getDataset();
        image = new DicomImage(dcmff, dset->getOriginalXfer());
        tmp = dcmImage;
        dcmImage = image;
        delete tmp;
        dset->findAndGetFloat64(DCM_WindowWidth, winWidth);
        dset->findAndGetFloat64(DCM_WindowCenter, winCenter);
        defCenter = winCenter;
        defWidth = winWidth;
        dcmImage->setWindow(winCenter, winWidth);
        if (cachedCenter == winCenter && cachedWidth == winWidth)
            dcm2bmpHelper(*dcmImage, cachedPixmap);
        if (!filePath.isEmpty())
            return EC_Normal == dcmff->saveFile(QString("%1/%2").arg(mainWindow->getDbConfig().imagePath, filePath).toLocal8Bit().data());
    }
    return false;
}

bool ImageInstanceData::dcm2bmpHelper(DicomImage &dcmImage, QPixmap &pixmap)
{
    BITMAPFILEHEADER lpfh;
    BITMAPINFOHEADER lpih;
    RGBQUAD palette[256];

    memset(&lpfh, 0, sizeof(BITMAPFILEHEADER));
    lpfh.bfType = 0x4d42;  //'B''M'
    lpfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(palette);


    memset(&lpih, 0, sizeof(BITMAPINFOHEADER));
    lpih.biSize = sizeof(BITMAPINFOHEADER);
    lpih.biWidth = dcmImage.getWidth();
    lpih.biHeight = dcmImage.getHeight();
    lpih.biCompression = BI_RGB;
    lpih.biBitCount = 8;
    lpih.biPlanes = 1;

    memset(palette, 0, sizeof(palette));
    for (int i = 0; i < 256; ++i) {
        palette[i].rgbBlue = i;
        palette[i].rgbGreen = i;
        palette[i].rgbRed = i;
    }

    void *pDIB = NULL;

    int size = dcmImage.createWindowsDIB(pDIB, 0, 0, 8, 1, 1);

    //lpih.biSizeImage = size;
    lpfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(palette) + size;

    QByteArray bmp;
    bmp.append((char*)&lpfh, sizeof(BITMAPFILEHEADER));
    bmp.append((char*)&lpih, sizeof(BITMAPINFOHEADER));
    bmp.append((char*)palette, sizeof(palette));
    bmp.append((char*)pDIB, size);

    delete pDIB;
    return pixmap.loadFromData(bmp);
}

bool ImageInstanceData::getPixmap(QPixmap &pixmap)
{
    bool retval = true;

    if (rawType && (!rawImage) && hasRawImage()) {
        OFCondition cond;
        rawDcmff = new DcmFileFormat;
        cond = rawDcmff->loadFile(QString("%1/%2").arg(mainWindow->getDbConfig().imagePath, rawFile).toLocal8Bit().data());
        if (cond.good()) {
            rawImage = new DicomImage(rawDcmff, rawDcmff->getDataset()->getOriginalXfer());
        } else {
            delete rawDcmff;
            rawDcmff = 0;
        }
    }

    if (isNormal()) {
        if (rawType) {
            if (rawWidth < 1) rawWidth = 1;
            if (rawCenter != cachedRawCenter || rawWidth != cachedRawWidth) {
                cachedRawCenter = rawCenter;
                cachedRawWidth = rawWidth;
                rawImage->setWindow(rawCenter, rawWidth);
                retval = dcm2bmpHelper(*rawImage, cachedRawPixmap);
            }
            pixmap = cachedRawPixmap;
        } else {
            if (winWidth < 1) winWidth = 1;
            if (winCenter != cachedCenter || winWidth != cachedWidth) {
                cachedCenter = winCenter;
                cachedWidth = winWidth;
                dcmImage->setWindow(winCenter, winWidth);
                retval = dcm2bmpHelper(*dcmImage, cachedPixmap);
            }
            pixmap = cachedPixmap;
        }
    } else retval = false;

    return retval;
}

bool ImageInstanceData::getPixmap(const QString &dicomFile, QPixmap &pixmap)
{
    ImageInstanceData image(dicomFile);
    return image.getPixmap(pixmap);
}

DicomImage* ImageInstanceData::createClippedImage(const QRect &rect, int angle, bool hflip, bool vflip, bool inverted)
{
    DicomImage *image;
    if (rawType) image = rawImage;
    else image = dcmImage;
    if (!image) return image;

    int ret = 1;
    Uint16 pvalue = image->getPhotometricInterpretation()==EPI_Monochrome1?65535:0;
    DicomImage *newImage = image->createClippedImage(rect.left(), rect.top(), rect.width(), rect.height(), pvalue);
    if (newImage) {
        if (ret && angle) ret = newImage->rotateImage(angle%360);
        if (ret && hflip) ret = newImage->flipImage(1, 0);
        if (ret && vflip) ret = newImage->flipImage(0, 1);
        if (ret && inverted) ret = newImage->setPolarity(EPP_Reverse);
        if (!ret) {
            delete newImage;
            newImage = 0;
        }
    }
    return newImage;
}

QStringList ImageInstanceData::getAnnotations()
{
    QString topleft;
    QString topright;
    QString bottomleft;
    QString bottomright;

    topleft = QString("Im: %1  Se: %3\n%4%5Scalor: 100mm").arg(instanceNumber, seriesNumber,
                          studyDes.isEmpty()?"":("\n"+studyDes),
                          seriesDes.isEmpty()?"":("\n"+seriesDes));
    topright = QString("%1%2%3%4").arg(patientName.isEmpty()?"":(patientName+"\n"),
                                       patientID.isEmpty()?"":(patientID+"\n"),
                                       patientSex.isEmpty()?"":(patientSex+"/"), patientAge);

    QString bodyInfo = QString("%1-%2").arg(bodyPart, bodyPos);
    if (bodyInfo.startsWith("-")) bodyInfo.remove(0, 1);
    if (bodyInfo.endsWith("-")) bodyInfo.remove(bodyInfo.size()-1, 1);
    bottomleft = QString("%1%2").arg(bodyInfo.isEmpty()?"":(bodyInfo+"\n"), manufacturer);
    bottomright = QString("%1mA %2mS %3kV%4%5").arg(QString::number(mA), QString::number(mS), QString::number(kvp),
                                  acquisitionTime.isValid()?("\n"+acquisitionTime.toString(DATETIME_SQLITE_FORMAT)):"",
                                  institution.isEmpty()?"":("\n"+institution));
    return QStringList() << topleft << topright << bottomleft << bottomright;
}

uint ImageInstanceData::getPixelValue(long x, long y, EP_Representation &r) const
{
    DicomImage *image;
    if (rawType) image = rawImage;
    else image = dcmImage;

    if (image) {
        const DiPixel* pixel = image->getInterData();
        if (pixel && (x < (long)image->getWidth()) && (x >= 0)
                && (y < (long)image->getHeight()) && (y >= 0)) {
            r = pixel->getRepresentation();
            switch (r) {
            case EPR_Sint8:
            case EPR_Uint8:
                return *((char*)(pixel->getData()) + (y * image->getWidth() + x));
            case EPR_Sint16:
            case EPR_Uint16:
                return *((short*)(pixel->getData()) + (y * image->getWidth() + x));
            case EPR_Sint32:
            case EPR_Uint32:
                return *((int*)(pixel->getData()) + (y * image->getWidth() + x));
            }
        }
    }

    r = (EP_Representation)-1;
    return 0;
}

bool ImageInstanceData::getPixSpacing(double &spacingX, double &spacingY) const
{
    if (isNormal()) {
        spacingX = pixelSpacingX;
        spacingY = pixelSpacingY;
        return true;
    } else return false;
}
